package com.hup.utils.commons;

import lombok.Getter;
import lombok.extern.log4j.Log4j2;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;

/**
 * 实现[字符串,数字]混合型字符串的排序功能;
 * 与window的文件名排序相近(受特殊字符和字符串切割次数的限制,无做到一模一样)
 *
 * @author hugan
 * @date 2020/5/3
 */
@Log4j2
public class NameSorter {

    /**
     * 根据target的name字段进行排序
     *
     * @param target 要排序的目标
     * @param nameGetter 字段getter
     * @apiNote name组成[数字][非数字][数字][非数字][后面都截取成一般字符串]
     */
    public static <E> void sortByName(List<E> target, Function<E, String> nameGetter) {
        target.sort(getComparator(target, nameGetter));
    }

    /**
     * @return 比较器
     * @apiNote 每次排序时都需要重新获取, 因为数据是会变化的;(如果排序数据不变化,则不用重新获取)
     */
    public static <E> Comparator<E> getComparator(Iterable<E> target, Function<E, String> nameGetter) {
        //对要排序的字符串进行转化,避免sort时大量冗余的切割字符串
        HashMap<String, NamePiece> map = new HashMap<>();
        target.forEach(item -> map.computeIfAbsent(nameGetter.apply(item), NamePiece::new));
        return (o1, o2) -> {
            NamePiece n1 = map.get(nameGetter.apply(o1));
            NamePiece n2 = map.get(nameGetter.apply(o2));
            return n1.compareTo(n2);
        };
    }

    public static final Comparator<NameSortable> NAME_SORTER_COMPARATOR = new Comparator<NameSortable>() {
        @Override
        public int compare(NameSortable o1, NameSortable o2) {
            if (o1.getNamePiece() == null) initNamePiece(o1);
            if (o2.getNamePiece() == null) initNamePiece(o2);
            return o1.getNamePiece().compareTo(o2.getNamePiece());
        }

        void initNamePiece(NameSortable nameSortable) {
            nameSortable.setNamePiece(new NamePiece(nameSortable.getSortName()));
        }
    };

    public interface NameSortable {

        String getSortName();

        NamePiece getNamePiece();

        void setNamePiece(NamePiece namePiece);
    }

    /**
     * 字符串切割后的片段数据对象;
     * 片段的最大组成:[数字][非数字][数字][后面都截取成一般字符串]
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static class NamePiece implements Comparable<NamePiece> {

        /**
         * 中文拼音排序
         */
        private static final Collator CN_COMPARATOR = Collator.getInstance(Locale.CHINA);
        @Getter
        private ArrayList<Comparable> pieces = new ArrayList<>(4);

        /**
         * @param target 组成可能:
         * 数字,
         * 字符串,
         * [数字][非数字],
         * [数字][非数字][数字],
         * [数字][非数字][数字][非数字],
         * [非数字][数字],
         * [非数字][数字][非数字],
         * @apiNote 测试类:com.hup.utils.commons.RegexTest#testNameSorterRegex()
         */
        public NamePiece(String target) {
            if (target == null || target.isEmpty()) throw new IllegalArgumentException("属性不能为空字符串");
            int length = target.length();
            StringBuilder sb = new StringBuilder(length);//存储当前的片段

            int curIndex;//当前是第几片[0,1,2,3]
            boolean isPreviousNum;
            if (isPreviousNum = isNum(target.charAt(0))) {
                curIndex = 0;//第一位是数字,从num1开始
            } else {
                curIndex = 1;//第一位是字符串,从str1开始
            }
            //log.debug("curIndex={}", curIndex);
            for (int i = 0; i < length; i++) {
                char c = target.charAt(i);
                boolean isNum = isNum(c);

                if (isNum == isPreviousNum) {
                    //当前位和上一位是同类
                    sb.append(c);
                } else {
                    /*
                    不同类:
                    .将旧的sb放入对应片段中
                    .重置sb,并添加当前char
                    .curIndex后移
                    .刷新isPreviousNum
                     */
                    String previousStr = sb.toString();//上一个片段
                    if (isPreviousNum) {
                        int num = Integer.parseInt(previousStr);
                        if (curIndex == 0) {
                            //第1片数字
                            pieces.add(num);
                        } else if (curIndex == 2) {
                            //第2片数字
                            pieces.add(num);
                            //当前已经是第二片数字,后面的都截取成一个字符串
                            pieces.add(target.substring(i));
                            return;//结束
                        } else {
                            throw new IllegalArgumentException("算法异常?");
                        }
                    } else {
                        if (curIndex == 1) {
                            //第1片字符串
                            pieces.add(previousStr);
                        } else {
                            throw new IllegalArgumentException("算法异常?");
                        }
                    }
                    //上述步骤
                    sb.delete(0, sb.length());
                    sb.append(c);
                    curIndex++;
                    isPreviousNum = isNum;
                }
            }

            //遍历完后,最后一个片段的数据
            String previousStr = sb.toString();//上一个片段
            if (isPreviousNum) {
                int num = Integer.parseInt(previousStr);
                if (curIndex == 0) {
                    //第1片数字
                    pieces.add(num);
                } else if (curIndex == 2) {
                    //第2片数字
                    pieces.add(num);
                } else {
                    throw new IllegalArgumentException("算法异常?");
                }
            } else {
                if (curIndex == 1) {
                    //第1片字符串
                    pieces.add(previousStr);
                } else {
                    throw new IllegalArgumentException("算法异常?");
                }
            }
        }

        private boolean isNum(char c) {
            return c >= '0' && c <= '9';
        }

        @Override
        public int compareTo(NamePiece o2) {
            int size1 = pieces.size();
            int size2 = o2.pieces.size();
            int min = Math.min(size1, size2);
            for (int i = 0; i < min; i++) {
                Comparable p1 = pieces.get(i);
                Comparable p2 = o2.pieces.get(i);
                int res;
                if (p1.getClass().equals(p2.getClass())) {
                    //同类行,直接比较
                    if (p1 instanceof String) res = CN_COMPARATOR.compare(p1, p2);
                    else res = p1.compareTo(p2);
                } else {
                    /*
                    数字和字符比较:
                    return 数字<字符串
                    特殊处理: '_'开头的字符串<数字
                     */
                    String str = (String) (p1 instanceof String ? p1 : p2);
                    if (str.charAt(0) == '_') {
                        return -(p1 instanceof Integer ? -1 : 1);
                    } else {
                        return p1 instanceof Integer ? -1 : 1;
                    }
                }
                //当前两位不等,就返回结果;相等,就比较下一位
                if (res != 0) return res;
            }

            //前面n位都相等时,较短的在前面
            return size1 - size2;
        }

        @Override
        public String toString() {
            return pieces.toString();
        }

    }

}
